var life = {

	/**
	 * A reference to the main scene assistant.
	 */
	mainAssistant : null,

	/**
	 * Flag set to true when a game is in progress (used for pausing).
	 */
	gameInProgress : false,

	/**
	 * The 2D context of the canvas.
	 */
	ctx : null,

	/**
	 * This is a reference to the main game loop bound to the context of the
	 * game object. This is necessary to provide the proper execution context
	 * when the main game loop is executed form the interval.
	 */
	mainLoopBind : null,

	/**
	 * Binds for the key, screen, orientation change and stage event handlers.
	 */
	tapHandlerBind : null,
	keyDownBind : null,

	/**
	 * Reference to the interval that runs the main game loop.
	 */
	mainLoopInterval : null,

	/**
	 * actual data
	 */
	data : null,

	liveCellCounter : 0,
	generationCounter : 0,
	cols : 0,
	rows : 0,

	/**
	 * X/Y values to adjust all coordinates so things are drawn in the
	 * playfield.
	 */
	xAdj : 7,
	yAdj : 7,
	cellSize : 20,

	/**
	 * Holds coordinates of the left-upper corner that is displayed
	 */
	displayX : 0,
	displayY : 0,

	dataHeight : 100,
	dataWidth : 100,

	/**
	 * The game background image and its height.
	 */
	backgroundHeight : null,
	backgroundWidth : null,
	liveCell : null,
	deadCell : null,

	/**
	 * Reference to the currently opened dialog, if any.
	 */
	dialog : null,
	adminMode : false,
	saveSateAssistant : null,
	openSateAssistant : null,

	speed : 500,

	/**
	 * Initialize the game. This is called once at application startup.
	 */
	init : function() {
		depot.getVersion(this.firstTimeRun);
		depot.get("speed", this.processGetSpeed);

		this.saveSateAssistant = new SaveSateAssistant();
		this.openSateAssistant = new OpenSateAssistant();

		// Use screen height to calculate dimensions for various assets.
		this.backgroundHeight = Mojo.Environment.DeviceInfo.screenHeight - 80;
		this.backgroundWidth = Mojo.Environment.DeviceInfo.screenWidth
				- (this.xAdj * 2);

		// init data
		this.recalculate();
		this.createData();

		// Bind events
		this.tapHandlerBind = this.tapHandler.bind(this);
		this.keyDownBind = this.keyDown.bind(this);

		// Preload all images.
		this.liveCell = new Image();
		this.liveCell.src = "images/live-cell.png";

		this.deadCell = new Image();
		this.deadCell.src = "images/dead-cell.png";

		this.mainLoopBind = this.step.bind(this);

	}, /* End init(). */

	processGetSpeed : function(result) {
		if (result != null) {
			life.speed = result;
		}
	},

	firstTimeRun : function(result) {
		var currentVersion = Mojo.Controller.appInfo.version;
		if (result == null) {
			Mojo.Log.info("Depot: First time run of " + currentVersion);
			depot.add("version", currentVersion);
			patterns.save();
		} else {
			if (result != currentVersion) {
				Mojo.Log.info("Depot: Replacing " + result + " with "
						+ currentVersion);
				depot.add("version", currentVersion);
				patterns.save();
			}
		}
	},

	recalculate : function() {
		this.cols = Math.floor(this.backgroundWidth / this.cellSize);
		this.rows = Math.floor(this.backgroundHeight / this.cellSize);
		// adjust xAdj to make cells centered
		this.xAdj = Math
				.floor((Mojo.Environment.DeviceInfo.screenWidth - this.cols
						* this.cellSize) / 2);
	},

	createData : function() {
		this.data = new Array();
		for (x = 0; x < this.dataWidth; ++x) {
			this.data[x] = new Array();
			for (y = 0; y < this.dataHeight; ++y) {
				this.data[x][y] = 0;
			}
		}
	},

	drawEmptyBackground : function() {
		for (x = 0; x < this.cols; ++x) {
			for (y = 0; y < this.rows; ++y) {
				this.drawDead(x, y);
			}
		}
	},

	clear : function() {
		this.drawEmptyBackground();
		this.createData();
		this.liveCellCounter = 0;
		this.generationCounter = 0;
		this.mainAssistant.updateButtons(true, true, true);
	},

	step : function() {
		++this.generationCounter;
		var cellsChanged = 0;
		var newData = new Array();
		for (x = 0; x < this.dataWidth; ++x) {
			newData[x] = new Array();
			for (y = 0; y < this.dataHeight; ++y) {
				var neighbours = this.getNeighbours(x, y);
				if (this.data[x][y] == 1) {
					if (neighbours < 2 || neighbours > 3) {
						newData[x][y] = 0;
						this.decreaseCellCounter();
						++cellsChanged;
					} else {
						newData[x][y] = 1;
					}
				} else {
					if (neighbours == 3) {
						newData[x][y] = 1;
						this.increaseCellCounter();
						++cellsChanged;
					} else {
						newData[x][y] = 0;
					}
				}
			}
		}
		this.data = newData;
		if (cellsChanged == 0 && this.gameInProgress == true
				&& this.liveCellCounter > 0) {
			this.gameInProgress = false;
			clearInterval(this.mainLoopInterval);
			this.mainAssistant.btnRunModel.label = "Run";
			this.mainAssistant.updateButtons(false, false, true);
		}
		this.drawData();
	},

	drawData : function() {
		for (x = 0; x < this.cols; ++x) {
			for (y = 0; y < this.rows; ++y) {
				if (data[x + this.displayX][y + this.displayY] == 0) {
					this.drawDead(x, y);
				} else {
					this.drawLive(x, y);
				}
			}
		}
	},

	getNeighbours : function(x, y) {
		var result = 0;
		var left = x - 1;
		var top = y - 1;
		var right = x + 1;
		var bottom = y + 1;

		if (left >= 0) {
			if (top >= 0) {
				result += this.data[left][top];
			}
			result += this.data[left][y];
			if (bottom < this.dataHeight) {
				result += this.data[left][bottom];
			}
		}
		if (top >= 0) {
			result += this.data[x][top];
		}
		if (bottom <= this.dataHeight) {
			result += this.data[x][bottom];
		}
		if (right < this.dataWidth) {
			if (top >= 0) {
				result += this.data[right][top];
			}
			result += this.data[right][y];
			if (bottom <= this.dataHeight) {
				result += this.data[right][bottom];
			}
		}
		return result; 
	},

	run : function() {
		this.gameInProgress = true;
		Mojo.Log.info("Running. Speed: " + this.speed);
		this.mainLoopInterval = setInterval(this.mainLoopBind, this.speed);
	},

	pause : function() {
		this.gameInProgress = false;
		clearInterval(this.mainLoopInterval);
	},

	/**
	 * Handle screen tap events.
	 * 
	 * @param inEvent
	 *            The generated event object.
	 */
	tapHandler : function(inEvent) {
		if (this.dialog != null) {
			return;
		}

		if (inEvent.down.x > this.xAdj && inEvent.down.y > this.yAdj
				&& inEvent.down.y < this.backgroundHeight + this.yAdj - 1
				&& inEvent.down.x < this.backgroundWidth + this.xAdj - 1) {
			x = inEvent.down.x - this.xAdj;
			y = inEvent.down.y - this.yAdj;
			xcell = Math.floor(x / this.cellSize);
			ycell = Math.floor(y / this.cellSize);

			if (this.data[xcell][ycell] == 1) {
				// cell was live, die!
				this.decreaseCellCounter();
				this.data[xcell][ycell] = 0;
				this.drawDead(xcell, ycell);

			} else {
				// cell was dead, live!
				this.increaseCellCounter();
				this.data[xcell][ycell] = 1;
				this.drawLive(xcell, ycell);
			}
		}
	}, /* End tapHandler(). */

	decreaseCellCounter : function() {
		this.liveCellCounter--;
		if (this.liveCellCounter == 0) {
			this.mainAssistant.btnRunModel.disabled = true;
			this.mainAssistant.btnClearModel.disabled = true;
			this.mainAssistant.btnStepModel.disabled = true;
			if (this.gameInProgress == true) {
				this.gameInProgress = false;
				clearInterval(this.mainLoopInterval);
				this.mainAssistant.btnRunModel.label = "Run";
			}
			this.mainAssistant.updateButtons();
		}
	},

	increaseCellCounter : function() {
		if (this.liveCellCounter == 0) {
			this.mainAssistant.btnRunModel.disabled = false;
			this.mainAssistant.btnClearModel.disabled = false;
			this.mainAssistant.btnStepModel.disabled = false;
			this.mainAssistant.updateButtons();
		}
		this.liveCellCounter++;
	},

	drawLive : function(x, y) {
		this.drawCell(this.liveCell, x, y);

	},

	drawDead : function(x, y) {
		this.drawCell(this.deadCell, x, y);
	},

	drawCell : function(cell, xcell, ycell) {
		this.ctx.drawImage(cell, 1 + this.xAdj + xcell * this.cellSize, 1
				+ this.yAdj + ycell * this.cellSize, this.cellSize - 1,
				this.cellSize - 1);
	},

	keyDown : function(inEvent) {
		if (this.gameInProgress) {
			if (inEvent.originalEvent.keyCode > Mojo.Char.zero
					&& inEvent.originalEvent.keyCode <= Mojo.Char.nine) {
				this.pause();
				this.speed = (Mojo.Char.zero - inEvent.originalEvent.keyCode + 10) * 100;
				this.run();
			}
		} else if (this.dialog == null) {
			if (inEvent.originalEvent.ctrlKey == true) {
				switch (inEvent.originalEvent.keyCode) {
				case Mojo.Char.h: // help
					this.mainAssistant.helpPressed();
					break;

				case Mojo.Char.a: // adjust cell's size
					life.dialog = life.mainAssistant.controller.showDialog( {
						template : "input-cell-size-dialog",
						assistant : new InputCellAssistant(),
						preventCancel : true
					});
					break;
				case Mojo.Char.s: // save current state
					if (this.liveCellCounter > 0) {
						this.openSaveStateDialog();
					}
					break;
				case Mojo.Char.o: // open saved states
					this.openOpenStateDialog();
					break;
				case Mojo.Char.r:
					if (life.adminMode) {
						// reset depot
						life.dialog = life.mainAssistant.controller
								.showAlertDialog( {
									onChoose : function(value) {
										if (value == "reset") {
											depot.removeAll();
											life.firstTimeRun(null);
										}
										life.dialog = null;
									},
									title : $L("Reset Depot"),
									message : $L("Do you really want to reset depot?"),
									choices : [ {
										label : $L('No'),
										value : "no",
										type : 'affirmative'
									}, {
										label : $L("Reset!!!"),
										value : "reset",
										type : 'negative'
									}, {
										label : $L("Nevermind"),
										value : "cancel",
										type : 'dismiss'
									} ]
								});
					}
					break;
				case Mojo.Char.q:
					if (life.adminMode == true) {
						Mojo.Log.info("Admin mode off");
						life.adminMode = false;
					} else {
						Mojo.Log.info("Admin mode on");
						life.adminMode = true;
					}
					break;
				}
			}
		}
	},

	openSaveStateDialog : function() {
		life.dialog = life.mainAssistant.controller.showDialog( {
			template : "save-state-dialog",
			assistant : this.saveSateAssistant,
			preventCancel : true
		});
	},

	openOpenStateDialog : function() {
		Mojo.Log.info("openOpenStateDialog");
		life.dialog = life.mainAssistant.controller.showDialog( {
			template : "open-state-dialog",
			assistant : this.openSateAssistant,
			preventCancel : true
		});
	}

}; /* End game object. */